package stub

import (
	"fmt"
	"gitee.com/CloudGuan/rpc-go-backend/idlrpc/protocol"
	"gitee.com/CloudGuan/rpc-go-backend/idlrpc/stubcall"
	"gitee.com/CloudGuan/rpc-go-backend/idlrpc/transport"
	"runtime/debug"
	"sync"
	"sync/atomic"
)

//@title server stub 封装
//@detail 网络层消息第一返回就是server stub，接受请求消息

type ServerStub struct {
	servuuid    uint64                     //service id
	servid      uint32                     //实例id
	servname    string                     //service name
	status      uint32                     //服务状态 配合zookeeper 开发
	refcount    int32                      //引用计数 有多少服务正在被调用
	stubcallmgr *stubcall.SrvStubCallMgr   //管理器指针
	stubimpl    SrvStub                    //server stub 实现 生成代码扩展
	waitGroup   sync.WaitGroup             //等待所有协程完成
	callQueue   chan *stubcall.SrvStubCall //调用队列

}

func NewServerStub(stub SrvStub, stubcallmgr *stubcall.SrvStubCallMgr) *ServerStub {
	return &ServerStub{
		stub.GetUUID(),
		0,
		stub.GetServiceName(),
		protocol.SERVICE_RESOLVED,
		0,
		stubcallmgr,
		stub,
		sync.WaitGroup{},
		make(chan *stubcall.SrvStubCall, 1024),
	}
}

func (stub *ServerStub) StartLoop() {
	if stub.stubimpl == nil {
		//这里要求必须 要先有stubImpl，没有这里必须直接panic
		panic(fmt.Sprintf("[ServieceStub] %s,%u,0 start service process error, imple is nil !!!", stub.servname, stub.servuuid))
	}

	multco := stub.stubimpl.MultipleNum()
	if multco == 0 {
		multco = 16 //如果是零 就给个默认值
	}

	for i := uint32(0); i < stub.stubimpl.MultipleNum(); i++ {
		stub.waitGroup.Add(1)
		go stub.callLoop()
	}
}

func (stub *ServerStub) callLoop() {
	//如果panic 也要结束协程
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("[ServerStub] service call process error %s \n" + string(debug.Stack()))
		}
		stub.waitGroup.Done()
	}()

	for {
		call := <-stub.callQueue
		if call == nil {
			//TODO add some error log
			continue
		}
		stub.callMethod(call)
	}
}

func (stub *ServerStub) RegisterService(v interface{}) {
	if stub.stubimpl == nil {
		panic("Unkown Service type for " + stub.servname)
	}
	stub.stubimpl.RegisterImpl(v)
}

func (stub *ServerStub) Tick() {

}

//@title 调用方法 多协程
func (stub *ServerStub) callMethod(caller *stubcall.SrvStubCall) {
	if stub.stubcallmgr == nil {
		//TODO：add log
		return
	}
	//stub.stubcallmgr.AddCall(caller)
	//TODO checkout timeout
	//发送调用结果回去
	trans := caller.GetTransportID()
	if trans == nil || trans.IsClose() {
		//TODO 添加日志 处理trans 已经失效的情况
		return
	}

	//call impl
	//这里调用玩家得方法有panic 得风险，所以这里需要 recover
	defer func() {
		if r := recover(); r != nil {
			//记录相关的信息
			fmt.Printf("[idlrpc] service: %s Call Method: %s panic", stub.servname, stub.stubimpl.GetSignature(caller.GetMethodID()))
			//开始回收资源

			//发送调用结果回去

			//减少引用计数？
			stub.DecRef()
			//删除call
			//stub.stubcallmgr.DestroyCall(caller.GetServerCallID())
			//打印错误
			fmt.Println(r)
		}
	}()

	//TODO 判断transform的异常
	stub.IncRef()

	//同步方法
	resp, err := stub.stubimpl.CallMethod(caller.GetReqData())
	if err != nil {
		//TODO 返回对应异常的错误码 替换错误库
		//build 一个错误Head回去
	}

	//如果是oneway这里要转发
	if !stub.stubimpl.IsOneWay(caller.GetMethodID()) {
		//根据错误码构建返回的header 走到这一步了怎么都需要返回
		var errcode uint32
		if resp == nil || err != nil {
			errcode = protocol.IDL_SERVICE_ERROR
		} else {
			errcode = protocol.IDL_SUCCESS
		}

		protocol.BuildRespHeader(resp, caller.GetServiceID(), caller.GetClientCallID(), errcode)

		resppkg, pkglen := protocol.PackRespMsg(resp)
		if resppkg == nil || pkglen == 0 {
			//TODO 添加序列化错误
			return
		}
		trans.Send(resppkg)
	}
	//减少引用计数？
	stub.DecRef()

	//TODO 发回主协程删除 call 不然会有问题
	//stub.stubcallmgr.DestroyCall(caller.GetServerCallID())
	return
}

func (stub *ServerStub) onLoad() {

}

func (stub *ServerStub) onDestroy() {

}

func (stub *ServerStub) SetStatus(status uint32) {
	stub.status = status
}

func (stub *ServerStub) Status() uint32 {
	return stub.status
}

//@title 将call方法压入队列，方便别的协程调用
//@detial 主协程调度
func (stub *ServerStub) CallStub(trans transport.Transport, req *protocol.RequestPackage) {
	if stub == nil {
		return
	}
	call := stubcall.NewSrvStubCall(req.Header.ServiceUUID, stub.servid, req.Header.CallID, req.Header.MethodID, trans)
	if call == nil {
		//TODO add error log
		return
	}
	call.SetReqData(req)
	stub.callQueue <- call
}

func (stub *ServerStub) IncRef() {
	atomic.AddInt32(&stub.refcount, 1)
}

func (stub *ServerStub) DecRef() {
	atomic.AddInt32(&stub.refcount, -1)
}
